#pragma once 

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <unordered_map>
#include <mutex>

#include "util.hpp"
#include "log.hpp"

#define X 10
#define Y 1

namespace ns_index
{
    typedef struct document_information
    {
        //文档的标题
        std::string title;

        //文档对应的去标签之后的内容
        std::string content;

        //文档所对应的官网的url
        std::string url;

        //文档的id
        uint64_t doc_id;
    }doc_info_t;

    typedef struct inverted_element
    {
        //文档的id
        uint64_t doc_id;

        //关键字
        std::string word;

        //权值
        int weight;
    }inver_elem_t;

    typedef std::vector<inver_elem_t> inverted_list;

    class index
    {
    public:
        //根据doc_id找到文档内容
        doc_info_t* get_forward_index(uint64_t doc_id)
        {
            if (doc_id >= forward_index.size())
            {
                std::cerr << "doc_id out of range!" << std::endl;
                return nullptr;
            }
            
            return &forward_index[doc_id];
        }

        //根据关键字string,获得倒排拉链
        inverted_list* get_inverted_list(const std::string& word)
        {
            auto it = inverted_index.find(word);
            if (it == inverted_index.end())
            {
                LOG(WARNING, word + " have no inverted_list!");
                return nullptr;
            }
            return &(it->second);
        }

        //根据去标签和格式化之后的文档,构建正排和倒排索引
        //parse处理完毕的数据交给我
        bool build_index(const std::string& input_src_path)
        {
            std::ifstream in(input_src_path, std::ifstream::in | std::ifstream::binary); 
            if (!in.is_open())
            {
                std::cerr << input_src_path << " open error!" << std::endl;
                return false;
            }

            std::string line;
            int count = 0;
            while(getline(in, line))
            {
                doc_info_t* doc = build_forward_index(line);
                if (doc == nullptr)
                {
                    std::cerr << "build forward index error" << std::endl;
                    continue;
                }
                build_inverted_index(*doc);
                count++;
                if (count % 50 == 0)
                {
                    LOG(NORMAL, "当前已经建立索引的文档 : " + std::to_string(count));
                }
            }
            return true;
        }

    private:
        doc_info_t* build_forward_index(const std::string& line)
        {
            //1. 解析line,进行字符串切分
            //line -> number of string -> title/content/url 
            std::vector<std::string> results;
            //行内分隔符
            const std::string separator = "\3";
            ns_util::string_util::split_string(line, &results, separator);
            if (results.size() != 3)
            {
                return nullptr;
            }

            //2.把分割好的字符串填充到doc_info_t中
            doc_info_t doc;
            doc.title = results[0];
            doc.content = results[1];
            doc.url = results[2];
            //先进行保存doc_id,再进行插入
            //对应的id就是当前doc在vector的下标
            doc.doc_id = forward_index.size();

            //3.插入到正排索引的vector当中去
            //可以用移动构造
            forward_index.push_back(std::move(doc));
            //最后一个就是刚刚进行插入的doc_info_t
            return &forward_index.back();
        }

        bool build_inverted_index(const doc_info_t& doc)
        {
            //传入进行的doc具有的成员变量有
            //{
            //    title;
            //    content;
            //    url;
            //    doc_id;
            //}
            
            struct word_count
            {
                int title_count;
                int content_count;

                word_count() : title_count(0), content_count(0){}
            };

            //用来暂时保存词频的映射表
            std::unordered_map<std::string, word_count> word_map;

            //对标题进行分词
            std::vector<std::string> title_words;
            ns_util::jieba_util::cut_string(doc.title, &title_words);

            //对标题进行词频统计
            for(auto s : title_words)
            {
                //搜索引擎在搜索的时候是不区分大小写的
                //所有我们这里把所有的内容转换成为小写
                boost::to_lower(s);
                //如果这个词存在就获取然后title_count++
                //如果不存在就新建
                word_map[s].title_count++;
            }

            //对文档内容进行分词
            std::vector<std::string> content_words;
            ns_util::jieba_util::cut_string(doc.content, &content_words);

            //对文档内容进行词频统计
            for (auto s : content_words)
            {
                boost::to_lower(s);
                word_map[s].content_count++;
            }
            
            //构建倒排拉链
            for (auto& word_pair : word_map)
            {
                inverted_element inver_elem;
                //这里就体现了doc_info_t里面设计doc_id好处了
                //inverted_element也能容易拿到doc_id
                inver_elem.doc_id = doc.doc_id;
                inver_elem.word = word_pair.first;
                //我们这里对于关键字在一篇文章中权值是直接按照
                //标题中出现是文章中出现的10倍
                //十分简单的直接计算的
                inver_elem.weight = X * word_pair.second.title_count + 
                    Y * word_pair.second.content_count;
                
                //根据关键字拿到倒排拉链的引用
                inverted_list& inver_list = inverted_index[word_pair.first];
                //对倒排拉链进行修改
                inver_list.push_back(std::move(inver_elem));

            }
            return true;
        }

    public:
        static index* get_instance()
        {
            //这里两层if的原因是减少上锁的次数
            if (instance == nullptr)
            {
                mtx.lock();
                if (instance == nullptr)
                {
                    instance = new index();
                }
                mtx.unlock();
            }
            return instance;
        }

    private:
        index(){}

        index(const index&) = delete;

        index& operator=(const index&) = delete;

    private:
        //正排索引的数据结构用数组,因为数据的下标天然就可以作为文档的ID
        std::vector<doc_info_t> forward_index;

        //倒排索引一定是一个关键字和一组(一个)inverted_element对应
        //这里是关键字和倒排拉链的映射关系
        std::unordered_map<std::string, inverted_list> inverted_index;

        //因为索引占用内存空间较大,且只需要生成一次足够了
        //所以我们这里采用单例模式
        static index* instance;

        //获取单例的时候,如果是多线程存在线程安全的问题
        //所以这里需要有互斥锁
        static std::mutex mtx;
    };

    index* index::instance = nullptr;
    std::mutex index::mtx;
    
}
